<?php
/**
 * Copyright 2009 Joe Dotoff
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/** HttpRequest */
require_phc(PDOG_LIB . '/Http/HttpRequest.php');
/** HttpResponse */
require_phc(PDOG_LIB . '/Http/HttpResponse.php');

/**
 * SimpleHttpd class
 * A simple HTTPD server implemented in PHP
 *
 * @package     PDog
 * @subpackage  Http
 * @author      Joe D. <nonultimate@gmail.com>
 * @license     Apache License Version 2.0
 * @version     $Id: SimpleHttpd.php 33 2009-04-21 01:03:44Z nonultimate $
 */
class SimpleHttpd
{
    /**
     * The port of httpd service
     *
     * @var int
     */
    protected $_port;
    /**
     * The document root path
     *
     * @var string
     */
    protected $_docroot;
    /**
     * Document index files
     *
     * @var array
     */
    protected $_index;
    /**
     * Default welcome message
     *
     * @var string
     */
    protected $_msg;
    /**
     * Constructor
     *
     * @param int  $port  Port to serve
     */
    public function __construct($port = null)
    {
        // Set default port
        $this->_port    = 8080;
        // Set default document root
        $this->_docroot = './';
        // Set default document index files
        $this->_index   = array('index.html');
        // Set default welcome message
        $this->_msg     = '<html><head><title>Simple HTTPD</title></head><body><h1>Simple HTTPD</h1></body></html>';
        if (null !== $port) {
            $this->_port = $port;
        }
    }
    /**
     * Listen on a port
     *
     * @param  int  $port  Port to listen
     * @return void
     */
    public function listen($port)
    {
        $this->_port = $port;
    }
    /**
     * Set default message if page not found
     *
     * @param  string  $msg  The default message
     * @return void
     */
    public function setMessage($msg)
    {
        $this->_msg = $msg;
    }
    /**
     * Set up document root path
     *
     * @param  string $path  The path for document root
     * @return void
     */
    public function setDocroot($path)
    {
        if (!file_exists($path)) {
            die('The path of the document root not exists');
        }
        $this->_docroot = realpath($path);
    }
    /**
     * Set document index files
     *
     * @param  array  $index  Array of index files
     * @return void
     */
    public function setIndex($index)
    {
        $this->_index = $index;
    }
    /**
     * Start httpd server
     *
     * @return void
     */
    public function start()
    {
        // Set script execution time with no limitation
        set_time_limit(0);
        $master = array();
        $socket = stream_socket_server("tcp://0.0.0.0:".$this->_port, $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN);
        if (!$socket) {
            die("$errstr ($errno)");
        }
        $master[] = $socket;
        $read = $master;
        while (true) {
            $read = $master;
            $sock_changed = stream_select($read, $w = null, $e = null, 5);
            if (false === $sock_changed) {
                break;
            }
            for ($i = 0; $i < $sock_changed; ++$i) {
                if ($read[$i] === $socket) {
                    $conn = stream_socket_accept($socket);
                    $master[] = $conn;
                } else {
                    $sock_data = fread($read[$i], 1024);
                    if (0 === strlen($sock_data)) {
                        $key_to_del = array_search($read[$i], $master, true);
                        fclose($read[$i]);
                        unset($master[$key_to_del]);
                    } else if (false === $sock_data) {
                        $key_to_del = array_search($read[$i], $master, true);
                        unset($master[$key_to_del]);
                    } else {
                        // Create a HttpRequest handler
                        $request = new HttpRequest();
                        $response = new HttpResponse();
                        $request->getHandler($read[$i], $sock_data);
                        $response->getHandler($read[$i]);

                        $path = '';
                        if ('/' === $request->getRequestURI()) {
                            foreach ($this->_index as $file) {
                                if (file_exists($this->_docroot . DIRECTORY_SEPARATOR . $file)) {
                                    $path = $this->_docroot . DIRECTORY_SEPARATOR . $file;
                                    break;
                                }
                            }
                        } else {
                            if (file_exists($this->_docroot.$request->getScriptName())) {
                                $path = $this->_docroot.$request->getScriptName();
                            }
                        }
                        if ('' !== $path) {
                            $response->setOutputFile($path);
                        } else {
                            $size = strlen($this->_msg);
                            $response->setState(404);
                            $response->setHeader("Content-Length", $size);
                            $response->write($this->_msg);
                        }
                        $access_log = $request->getRemoteAddr()
                            .' ['
                            .date('n/M/Y:H:i:s O')
                            .'] - "'
                            .$request->getMethod()
                            .' '
                            .$request->getRequestURI()
                            .' '
                            .$request->getVersion()
                            .'" '
                            .$response->getState()
                            .' '
                            .$response->getContentLength()
                            ."\n";
                        echo $access_log;
                        $response->close();
                        unset($master[array_search($read[$i], $master)]);
                    }
                }
            }
        }
    }
}
?>